import population
import species

class DualPop(object):
    """Simply routes calls to two populations/rankings.

        Select is done alternatingly.
    """
    def __init__(self):
        self.performance = population.Population()
        self.species = species.Species()
        self.species.make_extinct = self.make_extinct
        self.which = True

        class PredPop(population.Population):
            """An internal Population class that manages the population in
               terms of killing unfit individuals.
            """
            def __init__(self2):
                population.Population.__init__(self2)
                self2.tryAvg = 2




        class SpecPop(population.Population):
            """An internal Population class that manages a species.

               Is adapted to route calls to the kill function via self (the DualPop).
               Implements a neighbor-finding algorithm.
            """
            def __init__(self2, layers):
                population.Population.__init__(self2)
                self2.extend([reproduce.GeneratorEntry(self, layers),\
                                reproduce.MatingEntry(self)])
                self2.tryAvg = 3

                # TODO: expose this:
                # make seed_size simply the number of neurons
                seed_size = 0
                for l in layers:
                    seed_size += l

                self2.seed_size = seed_size

            def _kill(self2, *args):
                population.Population.kill(self2, *args)

            def kill(self2, *args):
                self.kill(*args)

            def add(self2, entry, value):
                    population.Population.add(self2, entry, value)
                    self2.update_neighbors(entry)

            def update(self2, entry):
                population.Population.update(self2, entry)
                self2.update_neighbors(entry)

            def update_neighbors(self2, entry):
                """Updates the list of neighbors of a certain entry."""
                nhood_size = 1 + len(self2) / len(entry)
                l = len(entry.neighbors)
                if l < nhood_size:
                    nbs = [self2.select(True) for i in xrange(nhood_size - l)]
                    entry.neighbors.update(nbs)

                big_n = set(entry.neighbors)
                for n in entry.neighbors:
                    big_n.update(n.neighbors)

                new_nhood = sorted(big_n, entry.neighbor_cmp)[:nhood_size]
                if not new_nhood == entry.neighbors:
                    entry.neighbors = new_nhood
                    self2.update_neighbors(entry)
                else:
                    entry.neighbors = new_nhood




        self.species.setup_pop = SpecPop

    def select(self):
        """Routes select alternating to both populations (views)."""
        self.which = not self.which
        if self.which:
            return self.performance.select()
        else:
            return self.species.select()

    def add(self, entry, value):
        self.performance.add(entry, value)
        self.species.add(entry, value)

    def valuate(self, entry, value):
        self.performance.valuate(entry, value)
        self.species.valuate(entry, value)

    def kill(self, entry):
        self.performance.kill(entry)
        self.species._kill(entry)

    def __len__(self):
        # TODO raise exception if size isn't same???
        return len(self.performance)

    def make_extinct(self, pop):
        """Gets rid of all specimen of one species."""
        self.species.killPop(pop)
        for entry in pop:
            if not isinstance(entry, population.SpecialEntry):
                self.performance.kill(entry)

    def findMate(self, entry):
        if self.species.by_signature.has_key(entry.layers):
            return self.species.by_signature[entry.layers].select(True)
        else:
            return entry
